﻿//Copyright 2011 - Jonathan Allen

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;

namespace Granite.Collections
{
    /// <summary>
    /// Utility methods for collection classes. 
    /// </summary>
    public static class CollectionUtilities
    {

        /// <summary>
        /// Adds a list of values into the target collection. 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="target"></param>
        /// <param name="list"></param>
        public static void AddRange<T>(this ICollection<T> target, IEnumerable<T> list)
        {
            if (target == null)
                throw new ArgumentNullException("target", "target is null.");
            if (list == null)
                throw new ArgumentNullException("list", "list is null.");
            Contract.EndContractBlock();

            foreach (var item in list)
                target.Add(item);
        }

        /// <summary>
        /// Adds a list of values into the target collection. 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="target"></param>
        /// <param name="list"></param>
        public static void AddRange<T>(this ICollection<T> target, params T[] list)
        {
            if (target == null)
                throw new ArgumentNullException("target", "target is null.");
            if (list == null)
                throw new ArgumentNullException("list", "list is null.");
            Contract.EndContractBlock();

            foreach (var item in list)
                target.Add(item);
        }

        /// <summary>
        /// Inserts a list of values into the target collection. 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="target"></param>
        /// <param name="startingIndex"></param>
        /// <param name="list"></param>
        /// <remarks>This isn't as fast as a real InsertRange, it just adds one item at a time.</remarks>
        [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "startingIndex")]
        public static void InsertRange<T>(this IList<T> target, int startingIndex, IEnumerable<T> list)
        {
            if (target == null)
                throw new ArgumentNullException("target", "target is null.");
            if (list == null)
                throw new ArgumentNullException("list", "list is null.");
            if (startingIndex < 0)
                throw ExceptionUtilities.ArgumentOutOfRangeException("startingIndex", startingIndex, "startingIndex must be >= 0");
            if (startingIndex > target.Count)
                throw ExceptionUtilities.ArgumentOutOfRangeException("startingIndex", startingIndex, "startingIndex must be <= target.Count");
            Contract.EndContractBlock();

            var index = startingIndex;
            foreach (var item in list)
            {
                target.Insert(index, item);
                index = Math.Min(index + 1, target.Count); //this is needed in case the collection is filtering out duplicates
            }
        }

        /// <summary>
        /// Removes count items from the collection
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list"></param>
        /// <param name="startingIndex"></param>
        /// <param name="count"></param>
        /// <remarks>This isn't as fast as a real RemoveRange, it just removes one item at a time.</remarks>
        [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "startingIndex")]
        public static void RemoveRange<T>(this IList<T> list, int startingIndex, int count)
        {
            if (list == null)
                throw new ArgumentNullException("list", "list is null.");
            if (startingIndex < 0)
                throw ExceptionUtilities.ArgumentOutOfRangeException("startingIndex", startingIndex, "startingIndex must be >= 0");
            if (startingIndex >= list.Count)
                throw ExceptionUtilities.ArgumentOutOfRangeException("startingIndex", startingIndex, "startingIndex must be < list.Count");
            Contract.EndContractBlock();

            for (int i = 0; i < count; i++)
            {
                Contract.Assume(startingIndex < list.Count); //we are assuming that the list works correctly
                list.RemoveAt(startingIndex);
            }
        }


        /// <summary>
        /// Selects items from the collection based on a certain range
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list"></param>
        /// <param name="startingIndex"></param>
        /// <param name="count"></param>
        [Pure]
        public static List<T> SelectRange<T>(this IList<T> list, int startingIndex, int count)
        {
            if (list == null)
                throw new ArgumentNullException("list", "list is null.");

            if (startingIndex < 0 || startingIndex >= list.Count)
                throw ExceptionUtilities.ArgumentOutOfRangeException("startingIndex", startingIndex, "startingIndex must be between 0 and list.Count-1 inclusive.");

            if (startingIndex + count > list.Count)
                throw ExceptionUtilities.ArgumentOutOfRangeException("count", startingIndex, "startingIndex + count must be less than or equal to list.Count.");

            Contract.Ensures(Contract.Result<List<T>>() != null);
            Contract.Ensures(Contract.Result<List<T>>().Count == count);

            var result = new List<T>(count);

            for (int i = 0; i < count; i++)
            {
                Contract.Assume(i + startingIndex < list.Count);
                result.Add(list[i + startingIndex]);
            }

            Contract.Assume(result.Count == count);

            return result;
        }

    }
}
